#include "Buffer.h"
#include "OBuffer.h"
#include <string.h>

OBuffer::OBuffer() {

	isInitialized = false;
}

OBuffer::~OBuffer() {

	if (isInitialized) {

		//write content from buffer to file
		//fill rest of buffer with 0
		for (size_t i = bufferIndex; i < BUFFER_SIZE; i++) {
			if(currentBuffer == 0) {
				buffer0[i] = 0;
			}else if(currentBuffer == 1) {
				buffer1[i] = 0;
			}else if(currentBuffer == 2) {
				buffer2[i] = 0;
			}else{
				assert(false);
			}
		}

		flush();

		int tmp = 0;
		while(tmp != 3) {
			if(sem_getvalue(empty, &tmp) != 0) {
				puts("get value of semaphore empty failed");
			}
		}

		if(pthread_detach(thread) != 0) {
			puts("detach of thread failed");
		}
		if(pthread_cancel(thread) != 0) {
			puts("cancel of thread failed");
		}

		//close file
		close(file);

		// free memory of all three buffers
		free(buffer0);
		free(buffer1);
		free(buffer2);

		// free memory of semaphores
		sem_destroy(empty);
		sem_destroy(full);

		free(empty);
		free(full);
	}

}

// Initialize internal buffer
bool OBuffer::init(const char* outputfile) {

	isInitialized = true;

	//not yet finished
	isFinished = false;

	//enable all three buffers to get filled from input file
	buffer0isFull = true;
	buffer1isFull = true;
	buffer2isFull = true;

	empty = new sem_t;
	full = new sem_t;

	//initialize semaphores
	if (sem_init(empty, 0, 3) == -1) {
		fputs("Failed to initialize semaphore EMPTY \n", stderr);
		return false;
	}
	if (sem_init(full, 0, 0) == -1) {
		fputs("Failed to initialize semaphore FULL \n", stderr);
		return false;
	}

	//set first buffer (buffer 0) as active
	currentBuffer = 0;

	//set index to the beginning of buffer
	bufferIndex = 0;

	//align memory for the three buffers
	buffer0 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer0 == NULL) {
		fputs("Buffer 0 Memalign failed\n", stderr);
		return false;
	}
	buffer1 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer1 == NULL) {
		fputs("Buffer 1 Memalign failed\n", stderr);
		return false;
	}
	buffer2 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer2 == NULL) {
		fputs("Buffer 2 Memalign failed\n", stderr);
		return false;
	}

	//open input file
	file = open(outputfile, ODIRECT | O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (file == -1) {
		fputs("couldn't open input file\n", stderr);
		return false;
	}

	close(file);

	//open file  second time
	file = open(outputfile, ODIRECT | O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (file == -1) {
		fputs("couldn't open input file\n", stderr);
		return false;
	}

	//start thread
	if (startThread(&thread, this) == EXIT_FAILURE) {
		fputs("couldn't start thread\n", stderr);
		return false;
	}

	return true;

}

bool OBuffer::run() {

	// decrement the semaphore FULL
	sem_wait(full);
	if (buffer0isFull) {
		buffer0isFull = false;
		if (write(file, buffer0, BUFFER_SIZE) == -1) {
			fputs("couldn't write to file from buffer 0\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else if (buffer1isFull) {
		buffer1isFull = false;
		if (write(file, buffer1, BUFFER_SIZE) == -1) {
			fputs("couldn't write to file from buffer 1\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else if (buffer2isFull) {
		buffer2isFull = false;
		if (write(file, buffer2, BUFFER_SIZE) == -1) {
			fputs("couldn't write to file from buffer 2\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else {

		assert(false);
		//Error -> terminate thread
		pthread_exit(NULL);
	}
	// increment semaphore FULL
	sem_post(empty);

	return !isFinished;
}

void OBuffer::writeString(const char* printString) {

	// get length of string (string must be null terminated)
	size_t size = strlen(printString);

	for (size_t i = 0; i < size; i++) {
		//new buffer
		if (bufferIndex == BUFFER_SIZE) {
			//if current buffer is full (end of index) --> increment full buffers
			flush();

		}
		if (bufferIndex == 0) {
			// check if there is an empty buffer
			sem_wait(empty);
		}

		// old buffer
		if (currentBuffer == 0){
			buffer0[bufferIndex++] = printString[i];
		}else if (currentBuffer == 1){
			buffer1[bufferIndex++] = printString[i];
		}else if (currentBuffer == 2){
			buffer2[bufferIndex++] = printString[i];
		}else{
			assert(false);
		}


	}

}

void OBuffer::flush() {
	if (currentBuffer == 0){
		buffer0isFull = true;
	}else if (currentBuffer == 1){
		buffer1isFull = true;
	}else if (currentBuffer == 2){
		buffer2isFull = true;
	}else{
		assert(false);
	}

	bufferIndex = 0;
	currentBuffer = (currentBuffer + 1) % 3;

	sem_post(full);
}
